1 module hip.network; 2 import hip.console.log; 3 import hip.api.net.utils; 4 import hip.api.net.hipnet; 5 6 struct NetBufferStream 7 { 8 NetBuffer self; 9 10 size_t currentOffset; 11 12 /** 13 * 14 * Params: 15 * data = The data to append to this stream. The buffer is sliced to remove the size took from it. 16 * Returns: If it has already finished appending (it is based on how much data it expectes) 17 */ 18 bool appendData(ref ubyte[] data) 19 { 20 if(isFinished()) 21 return true; 22 if(expectedSize > buffer.length) 23 buffer.length = expectedSize; 24 25 size_t remainingSize = expectedSize - currentOffset; 26 size_t sizeToTake = remainingSize < data.length ? remainingSize : data.length; 27 28 buffer[currentOffset..currentOffset+sizeToTake] = data[0..sizeToTake]; 29 currentOffset+= sizeToTake; 30 data = data[sizeToTake..$]; 31 return currentOffset == expectedSize; 32 } 33 34 35 bool isFinished() const { return currentOffset == expectedSize; } 36 37 ///Resets it to a reusable state 38 void reset() 39 { 40 currentOffset = 0; 41 header = header.init; 42 } 43 44 alias self this; 45 } 46 47 48 interface INetworkBackend 49 { 50 /** 51 * 52 * Params: 53 * ip = The IP to connect 54 * onConnect = Used for sending messages on the connect. WARNING: If you're using NetController, do not call `connect` from that interface. 55 * id = ID of the address to connect to. Relevant when you're not using a P2P connection. Enforced when using websockets, since direct connection is unavailable. 56 * Returns: The connection status 57 */ 58 NetConnectStatus connect(NetIPAddress ip, void delegate() onConnect, uint id = NetID.server); 59 /** 60 * Tries to reconnect to the same address on initial connect. 61 */ 62 void attemptReconnection(); 63 ///Gets ID for that network connection. It can't change over its lifetime 64 uint getConnectionSelfID() const; 65 66 ///Gets ID for the currently target network connection. Changed with the setter 67 uint targetConnectionID() const; 68 ///Sets that network connection connected to the specified ID 69 void targetConnectionID(uint id); 70 71 bool isHost() const; 72 73 ///Sends the data by using a header 74 bool sendData(ubyte[] data); 75 76 void disconnect(); 77 78 size_t getData(ref ubyte[] tempBuffer); 79 NetConnectStatus status() const; 80 } 81 82 final class HipNetwork : INetwork 83 { 84 INetworkBackend netInterface; 85 NetInterface itf; 86 NetBufferStream currentStream; 87 NetBufferStream[] completedStreams; 88 NetBufferStream[] streamPool; 89 90 private NetConnectInfo connInfo; 91 private bool attemptedConnection; 92 private void delegate() onConnect; 93 94 this(NetInterface itf) 95 { 96 import hip.net.backend.initializer; 97 this.itf = itf; 98 netInterface = getNetworkImplementation(itf); 99 } 100 101 NetConnectStatus connect(NetIPAddress ip, void delegate(INetwork) onConnect, uint id = NetID.server) 102 { 103 if(!attemptedConnection) 104 attemptedConnection = true; 105 connInfo = NetConnectInfo(ip, id); 106 this.onConnect = (){onConnect(this);}; 107 108 return netInterface.connect(ip, this.onConnect, id); 109 } 110 111 bool isHost() const { return netInterface.isHost; } 112 uint getConnectionSelfID() const{return netInterface.getConnectionSelfID();} 113 uint targetConnectionID() const {return netInterface.targetConnectionID();} 114 void targetConnectionID(uint id){netInterface.targetConnectionID(id);} 115 116 private NetBufferStream getNetBufferStream(NetHeader header) 117 { 118 NetBufferStream ret; 119 if(streamPool.length > 0) 120 { 121 ret = streamPool[0]; 122 streamPool = streamPool[1..$]; 123 ret.header = header; 124 } 125 else 126 ret = NetBufferStream(NetBuffer(header)); 127 return ret; 128 } 129 130 /** 131 * Only returns true if the buffer has been completely loaded by the size expected by the package. 132 * 133 * This function implements a data getter API which does not block execution (if the underlying implementation is non-blocking ) 134 * Params: 135 * data = The data if this functions returns true 136 * Returns: True when package is fully received. False if not connected or still pending data 137 */ 138 bool getData() 139 { 140 if(netInterface.status == NetConnectStatus.waiting) 141 netInterface.connect(connInfo.ip, this.onConnect, connInfo.id); 142 if(netInterface.status == NetConnectStatus.attemptingReconnect) 143 { 144 hiplog("Trying to reconnect."); 145 netInterface.attemptReconnection(); 146 } 147 148 if(netInterface.status != NetConnectStatus.connected) 149 return false; 150 if(completedStreams.length != 0) 151 return true; 152 153 ubyte[4096] tempBufferData = void; 154 ubyte[] tempBuffer = tempBufferData; 155 156 157 size_t dataLength = getDataBackend(tempBuffer); 158 if(dataLength == 0) 159 return false; 160 if(dataLength < NetHeader.sizeof) 161 throw new Exception("Some bug occurred: Data received is less than a NetHeader size."); 162 163 while(dataLength != 0) 164 { 165 if(currentStream.isInvalid) 166 { 167 currentStream = getNetBufferStream(*cast(NetHeader*)(tempBuffer.ptr)); 168 dataLength-= NetHeader.sizeof; 169 tempBuffer = tempBuffer[NetHeader.sizeof..$]; 170 } 171 172 //If it didn't fill buffer, that means there is no data left hanging, return if we completed any stream 173 if(!currentStream.appendData(tempBuffer)) 174 return completedStreams.length != 0; 175 176 177 completedStreams~= currentStream; 178 dataLength-= currentStream.expectedSize; 179 currentStream = currentStream.init; 180 } 181 182 183 return completedStreams.length != 0; 184 } 185 186 /** 187 * WARNING: It is important to call freeBuffer at some time since getting the buffer won't remove it from the queue 188 * Returns: A buffer from the completed list. You should free it at some time so its memory can be reused. 189 */ 190 NetBuffer* getCompletedBuffer() const 191 { 192 if(completedStreams.length == 0) 193 throw new Exception("No data has been received yet. Wait for getData() to return true before calling that function."); 194 return cast(NetBuffer*)&completedStreams[0]; 195 } 196 197 /** 198 * Make that buffer available inside the buffer pool and saves memory 199 * Params: 200 * buffer = A buffer inside the completed list 201 */ 202 void freeBuffer(NetBuffer* b) 203 { 204 NetBufferStream* buffer = cast(NetBufferStream*)b; 205 for(int i = 0 ; i < completedStreams.length; i++) 206 if(buffer == &completedStreams[i]) 207 { 208 buffer.reset(); 209 completedStreams = completedStreams[0..i] ~ completedStreams[i+1..$]; 210 streamPool~= *buffer; 211 i--; 212 } 213 } 214 215 /** 216 * Gets the data that is in front of queue and converts the data to the expected one. 217 * The correct usage of this function is to use it after `getData()`. 218 * If it has no data, an exception will be thrown 219 * Params: 220 * data = Data from getData 221 * Returns: 222 */ 223 T getDataAsType(T)() 224 { 225 NetBufferStream* stream = getCompletedBuffer(); 226 ubyte[] data = stream.getFinishedBuffer(); 227 scope(exit) 228 freeBuffer(stream); 229 return interpretNetworkData(stream.header, data); 230 } 231 232 void sendDataRaw(ubyte[] data) 233 { 234 netInterface.sendData(data); 235 } 236 private size_t getDataBackend(ref ubyte[] data) 237 { 238 size_t ret = netInterface.getData(data); 239 if(ret != 0) 240 { 241 data = data[0..ret]; 242 toNetworkBytesInPlace(data); 243 } 244 return ret; 245 } 246 247 void disconnect() 248 { 249 if(status == NetConnectStatus.connected) 250 { 251 send_disconnect(this); 252 netInterface.disconnect(); 253 } 254 } 255 NetConnectStatus status() const { return netInterface.status; } 256 NetInterface getNetInterfaceType() const { return itf; } 257 NetConnectInfo getConnectInfo() const { return connInfo; } 258 259 } 260 261 private __gshared INetwork[] netConnections; 262 263 export extern(System) INetwork getNetworkInterface(NetInterface itf = NetInterface.automatic) 264 { 265 netConnections~= new HipNetwork(itf); 266 return netConnections[$-1]; 267 } 268 269 270 /** 271 * Disconnects every net connection that was loaded at the beginning. 272 */ 273 void disconnectNetwork() 274 { 275 foreach(conn; netConnections) 276 conn.disconnect(); 277 netConnections = null; 278 }